package utils

import (
	"bytes"
	"crypto/md5"
	rand2 "crypto/rand"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
	"hash"
	"io"
	"io/ioutil"
	"log"
	"math"
	"math/big"
	"math/rand"
	"net/http"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

// Int all int types interface
type Int interface {
	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~int | ~int8 | ~int16 | ~int32 | ~int64
}

// Float all float types interface
type Float interface {
	~float32 | ~float64
}

// IntFloatString int float string
type IntFloatString interface {
	~int | ~float32 | ~float64 | string
}

const TimeFormat = "2006-01-02 15:04:05"
const DateFormat = "20060102150405"
const DateNormalFormat = "2006-01-02"

// InMap check it is in array
func InMap[T IntFloatString](key T, data []T) bool {
	for _, value := range data {
		if key == value {
			return true
		}
	}
	return false
}

// MapKeyExists check map key exists
func MapKeyExists[T IntFloatString, E any](key T, data map[T]E) bool {
	if _, ok := data[key]; ok {
		return true
	}
	return false
}

// GetMapKeys get map keys
func GetMapKeys[T IntFloatString, E any](data map[T]E) []T {
	var keys []T
	for key, _ := range data {
		keys = append(keys, key)
	}
	return keys
}

// GetMapValues get map values
func GetMapValues[T any, E IntFloatString](data map[E]T) []T {
	var values []T
	for _, value := range data {
		values = append(values, value)
	}
	return values
}

// GetMapValue get map value by key
func GetMapValue[T IntFloatString, E any](key T, data map[T]E) E {
	if MapKeyExists(key, data) {
		return data[key]
	}
	return any("").(E)
}

// GetMapStringValue get map string value by key
func GetMapStringValue[T IntFloatString](key T, data map[T]any) string {
	if MapKeyExists(key, data) {
		value, _ := ConvAnyToString(data[key])
		return value
	}
	return ""
}

func ChuckSlice[T any](items []T, size int) [][]T {
	var chuckList [][]T
	var subList []T
	count := 0
	itemSize := len(items)
	size = Ternary(size >= itemSize, itemSize, size)
	for _, item := range items {
		if count < size {
			subList = append(subList, item)
			count++
		}
		if count == size {
			chuckList = append(chuckList, subList)
			count = 0
			subList = nil
		}
	}
	if len(subList) > 0 {
		chuckList = append(chuckList, subList)
	}
	return chuckList
}

// ColumnArray 通过结构体数据获取行数据
func ColumnArray[T any, E any](items []T, field string) []E {
	var result []E
	for _, item := range items {
		rt := reflect.TypeOf(item)
		if rt.Kind().String() != "struct" {
			continue
		}
		rv := reflect.ValueOf(item)
		for i := 0; i < rt.NumField(); i++ {
			if rt.Field(i).Name == field {
				result = append(result, rv.Field(i).Interface().(E))
			}
		}
	}
	return result
}

// PluckArray 通过结构体数据获取行数据
func PluckArray[T any, E any](items []T, key string, column string) map[string]E {
	var result map[string]E
	for _, item := range items {
		rt := reflect.TypeOf(item)
		if rt.Kind().String() != "struct" {
			continue
		}
		rv := reflect.ValueOf(item)
		var kv string
		var cv E
		for i := 0; i < rt.NumField(); i++ {
			if rt.Field(i).Name == key {
				rvf := rv.Field(i)
				switch rvf.Kind() {
				case reflect.String:
					kv = rvf.String()
					break
				case reflect.Uint:
					kv = fmt.Sprintf("%d", rvf.Uint())
					break
				case reflect.Int, reflect.Int64:
					kv = fmt.Sprintf("%d", rvf.Int())
					break
				case reflect.Float32, reflect.Float64:
					kv = fmt.Sprintf("%d", rvf.Float())
					break
				default:
					continue
				}
			}
			if rt.Field(i).Name == column {
				cv = rv.Field(i).Interface().(E)
				if kv != "" {
					continue
				}
			}
			result[kv] = cv
		}
	}
	return result
}

func Intersect[T IntFloatString](a []T, b []T) []T {
	var inter []T
	for _, _b := range b {
		if InMap[T](_b, a) {
			inter = append(inter, _b)
		}
	}
	return inter
}

func Different[T IntFloatString](a []T, b []T) []T {
	var inter []T
	for _, _b := range b {
		if !InMap[T](_b, a) {
			inter = append(inter, _b)
		}
	}
	return inter
}

// Ternary three unit judge
func Ternary[T any](condition bool, trueValue T, falseValue T) T {
	if condition {
		return trueValue
	} else {
		return falseValue
	}
}

// TrimHtml trim html tags
func TrimHtml(src string) string {
	re, _ := regexp.Compile("\\<[\\S\\s]+?\\>|\\'|\"")
	src = re.ReplaceAllStringFunc(src, func(s string) string {
		return ""
	})
	return strings.TrimSpace(src)
}

// GetCurrentPath get current path
func GetCurrentPath() string {
	dir, err := os.Getwd()
	if err != nil {
		log.Fatal(2, "get path failed : %v", err)
	}
	return strings.Replace(dir, "\\", "/", -1)
}

func IsEmpty[T any](st T) bool {
	std := reflect.ValueOf(st)
	for i := 0; i < std.NumField(); i++ {
		if !std.Field(i).IsZero() {
			return true
		}
	}
	return false
}

func ResponseJson(ctx *gin.Context, code int, msg string, data ...map[string]any) {
	result := make(map[string]any, 0)
	if len(data) > 0 {
		result = data[0]
	}
	ctx.JSON(200, gin.H{
		"code": code,
		"msg":  msg,
		"data": result,
	})
}

func ResponseError(ctx *gin.Context, err error) {
	ctx.JSON(200, gin.H{
		"code": 500,
		"msg":  err.Error(),
		"data": make(map[string]any, 0),
	})
}

func RandomString(lenNum int, options ...string) string {
	var charList = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
		"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
		"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}
	str := strings.Builder{}
	length := len(charList)
	for i := 0; i < lenNum; i++ {
		l := charList[rand.Intn(length)]
		str.WriteString(l)
	}
	prefix := ""
	if len(options) > 0 {
		prefix = options[0]
	}
	return prefix + str.String()
}

func RandomNumbers(lenNum int) string {
	var str string
	for i := 0; i < lenNum; i++ {
		result, _ := rand2.Int(rand2.Reader, big.NewInt(128))
		str += strconv.FormatInt(result.Int64(), 10)
	}
	return str
}

// MakeOrdNo 创建订单号
func MakeOrdNo(lenNum int, prefix string) string {
	date := time.Now().Format(DateFormat)
	ns := time.Now().UnixNano() / 1e6
	nss := strconv.FormatInt(ns, 10)
	nss = prefix + date + nss[len(nss)-6:] + RandomNumbers(4)
	nl := len(nss)
	return nss[0:Ternary[int](lenNum <= nl, lenNum, nl)]
}

func IsNumeric(str string) bool {
	_, err := strconv.Atoi(str)
	return err == nil
}

func DatetimeToTimestamp(datetime string) int64 {
	Loc, _ := time.LoadLocation("Asia/Shanghai")
	dateLen := len(datetime)
	timestamp, _ := time.ParseInLocation(TimeFormat[0:dateLen], datetime, Loc)
	return timestamp.Unix()
}

func Timestamp() int64 {
	now := time.Now()
	return now.Unix()
}

func GetJsonParam(ctx *gin.Context, std any) error {
	data, err := ctx.GetRawData()
	if err != nil {
		return err
	}
	fmt.Println("接收到的数据：", string(data))
	ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data))
	err = json.NewDecoder(ctx.Request.Body).Decode(std)
	if err != nil {
		return err
	}
	fmt.Println("解析数据：", std)
	return nil
}

func ConvAnyToString(value any) (string, error) {
	var _value string
	switch value.(type) {
	case int:
		_value = strconv.Itoa(value.(int))
		break
	case int64:
		_value = strconv.FormatInt(value.(int64), 10)
		break
	case float64:
		_value = strconv.FormatFloat(value.(float64), 'f', 3, 64)
		break
	case string:
		_value = value.(string)
		break
	default:
		return "", errors.New(fmt.Sprintf("未定义的类型: %T, 值：%v", value, value))
	}
	return _value, nil
}

func ConvAnyToInt(value any) (int, error) {
	var _value int
	switch value.(type) {
	case int:
		_value = value.(int)
		break
	case int64:
		_value = int(value.(int64))
		break
	case float64:
		_value = int(math.Floor(value.(float64)))
		break
	case string:
		_value, _ = strconv.Atoi(value.(string))
		break
	default:
		return 0, errors.New(fmt.Sprintf("未定义的类型: %T, 值：%v", value, value))
	}
	return _value, nil
}

func GetCurrentDate() (year int, month int, day int) {
	year, _month, day := time.Now().Date()
	month = int(_month)
	return
}

func MakeTime(year int, month int, day int, hour int, minute int, second int) (timestamp int64) {
	location := time.Now().Location()
	timestamp = time.Date(year, time.Month(month), day, hour, minute, second, 0, location).Unix()
	return
}

func ChunkById[T any](db *gorm.DB, model *T, chunkCount int, callback func(items []*T) error, idField string, args ...any) error {
	where := ""
	if len(args) > 0 {
		where = args[0].(string)
		args = args[1:]
	}
	var rows int64
	db.Model(model).Where(where, args...).Count(&rows)

	times := int(math.Ceil(float64(rows) / float64(chunkCount)))
	for i := 0; i < times; i++ {
		offset := i * times
		var chunk []*T
		db.Model(model).Where(where, args...).Order(idField + " asc").Limit(chunkCount).Offset(offset).Find(&chunk)
		err := callback(chunk)
		if err != nil {
			return err
		}
	}
	return nil
}

// Concurrency 并发处理数据、获取数据
// count     总记录数
// size      每次执行数
// unitLimit 单元并发数
// input     数据写chan
// output    获取chan数据
func Concurrency[T any](count int64, size int, unitLimit int, input func(page int, size int) T, output func(data T)) {
	ch := make(chan T, unitLimit)

	defer close(ch)
	pCount := int(math.Ceil(float64(count) / float64(size)))
	// 执行计数器，每次执行一个绥中周期消费
	stepRunCount := 0
	stepCount := int(math.Ceil(float64(pCount / unitLimit)))
	stepStateCount := 0
	for i := 0; i < pCount; i++ {
		go func(page int, size int) {
			defer func() {
				if err := recover(); err != nil {
					fmt.Println("has panic: ", err)
				}
			}()
			ch <- input(page, size)
		}(i, size)

		stepRunCount++
		if stepRunCount >= unitLimit || i+1 == pCount {
			// 写入dataFrame对象
			receiveCount := 0
			stepStateCount++
			for {
				records, ok := <-ch
				if !ok {
					fmt.Println("receive over")
					break
				}
				receiveCount++
				fmt.Println("正在从chan接收数据... total: [ ", stepStateCount, " / ", stepCount+1, " ], unit: [", receiveCount, " / ", stepRunCount, " ]")
				output(records)
				if receiveCount >= stepRunCount {
					break
				}
			}
			stepRunCount = 0
		}
	}
}

// ConcurrencySimple 并发简化版
// count 执行总数
// unitLimit 单元并发数
// handle 协程内执行
func ConcurrencySimple(count int64, unitLimit int, handle func(start int, end int)) {
	var wg sync.WaitGroup
	unitCount := int(math.Ceil(float64(count / int64(unitLimit))))
	stepRunCount := 0
	for i := 0; i < unitCount; i++ {
		curr := i * unitLimit
		overCount := Ternary(i+1 < unitCount, unitLimit, int(count-int64(curr)))
		if stepRunCount == 0 {
			wg.Add(overCount)
		}
		go func(start int, end int) {
			defer func() {
				wg.Done()
				if err := recover(); err != nil {
					fmt.Println("has panic: ", err)
				}
			}()
			handle(start, end)
		}(i, (i+1)*unitLimit)
		stepRunCount++
		if stepRunCount >= unitLimit || i+1 >= unitCount {
			wg.Wait()
			stepRunCount = 0
		}
	}
}

// GetFileHash calculate file hash value
func GetFileHash(file io.Reader, hashType string) (string, error) {
	var hs hash.Hash
	switch hashType {
	case "md5":
		hs = md5.New()
		break
	case "sha1":
		hs = sha1.New()
		break
	case "sha256":
		hs = sha256.New()
		break
	default:
		return "", errors.New(fmt.Sprintf("the hash %s type is not supported", hashType))
	}
	if _, err := io.Copy(hs, file); err != nil {
		return "", errors.New(err.Error())
	}
	hashString := hs.Sum(nil)
	hashValue := hex.EncodeToString(hashString)
	return hashValue, nil
}

// CheckRemoteFileExists 验证远程文件是否存在
func CheckRemoteFileExists(url string) bool {
	if url == "" {
		return false
	}
	resp, err := http.Head(url)
	if err != nil {
		return false
	}
	if resp.StatusCode == http.StatusOK {
		return true
	}
	return false
}

// IsEmptyString 辅助函数：判断字符串是否为空或空白
func IsEmptyString(v any) bool {
	str, ok := v.(string)
	return !ok || strings.TrimSpace(str) == ""
}

// CheckSign 通用验证、创建签名
func CheckSign(data map[string]any, config VerificationConfig, isBuild bool) error {
	var keys []string
	data["secret"] = config.SignSecret
	for k := range data {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	signList := make([]string, 0)
	for _, k := range keys {
		value := data[k]
		signList = append(signList, fmt.Sprintf("%s=%v", k, value))
	}
	delete(data, "secret")
	if config.Debug {
		fmt.Println("签名参数：", signList)
	}
	localSign := fmt.Sprintf("%x", sha1.Sum([]byte(strings.Join(signList, "&"))))
	if isBuild {
		data["sign"] = localSign
		return nil
	}
	if localSign != data["sign"] {
		return errors.New("验证签名失败" + Ternary(config.Debug, fmt.Sprintf("：传入：%s, 本地：%s", data["sign"], localSign), ""))
	}
	return nil
}
